/* ========================================================================
 * ZUI: tree.js [1.4.0+]
 * http://zui.sexy
 * ========================================================================
 * Copyright (c) 2016 cnezsoft.com; Licensed MIT
 * ======================================================================== */
define(function(require, exports, module) {


    (function($) {
        'use strict';

        var name = 'zui.tree'; // modal name
        var globalId = 0;

        // The tree modal class
        var Tree = function(element, options) {
            this.name = name;
            this.$ = $(element);

            this.getOptions(options);
            this._init();
        };

        var DETAULT_ACTIONS = {
            sort: {
                template: '<a class="sort-handler" href="javascript:;"><i class="icon icon-move"></i></a>'
            },
            add: {
                template: '<a href="javascript:;"><i class="icon icon-plus"></i></a>'
            },
            edit: {
                template: '<a href="javascript:;"><i class="icon icon-pencil"></i></a>'
            },
            "delete": {
                template: '<a href="javascript:;"><i class="icon icon-trash"></i></a>'
            }
        };

        function formatActions(actions, parentActions) {
            if(actions === false) return actions;
            if(!actions) return parentActions;

            if(actions === true) {
                actions = {add: true, "delete": true, edit: true, sort: true};
            } else if(typeof actions === 'string') {
                actions = actions.split(',');
            }
            var _actions;
            if($.isArray(actions)) {
                _actions = {};
                $.each(actions, function(idx, action) {
                    if($.isPlainObject(action)) {
                        _actions[action.action] = action;
                    } else {
                        _actions[action] = true;
                    }
                });
                actions = _actions;
            }
            if($.isPlainObject(actions)) {
                _actions = {};
                $.each(actions, function(name, action) {
                    if(action) {
                        _actions[name] = $.extend({type: name}, DETAULT_ACTIONS[name], $.isPlainObject(action) ? action : null);
                    } else {
                        _actions[name] = false;
                    }
                });
                actions = _actions;
            }
            return parentActions ? $.extend(true, {}, parentActions, actions) : actions;
        }

        function createActionEle(action, name, template) {
            name = name || action.type;
            return $(template || action.template).addClass('tree-action').attr($.extend({'data-type': name, title: action.title || ''}, action.attr)).data('action', action);
        }

        // default options
        Tree.DEFAULTS = {
            animate: null,
            initialState: 'normal', // 'normal' | 'preserve' | 'expand' | 'collapse',
            toggleTemplate: '<i class="list-toggle icon"></i>',
            // sortable: false, //
        };

        Tree.prototype.add = function(rootEle, items, expand, disabledAnimate, notStore) {
            var $e = $(rootEle), $ul, options = this.options;
            if($e.is('li')) {
                $ul = $e.children('ul');
                if(!$ul.length) {
                    $ul = $('<ul/>');
                    $e.append($ul);
                    this._initList($ul, $e);
                }
            } else {
                $ul = $e;
            }

            if($ul) {
                var that = this;
                if(!$.isArray(items)) {
                    items = [items];
                }
                $.each(items, function(idx, item) {
                    var $li = $('<li/>').data(item).appendTo($ul);
                    if(item.id !== undefined) $li.attr('data-id', item.id);
                    var $wrapper = options.itemWrapper ? $(options.itemWrapper === true ? '<div class="tree-item-wrapper"/>' : options.itemWrapper).appendTo($li) : $li;
                    if(item.html) {
                        $wrapper.html(item.html)
                    } else if($.isFunction(that.options.itemCreator)) {
                        var itemContent = that.options.itemCreator($li, item);
                        if(itemContent !== true && itemContent !== false) $wrapper.html(itemContent);
                    } else if(item.url) {
                        $wrapper.append($('<a/>', {href: item.url}).text(item.title || item.name));
                    } else {
                        $wrapper.append($('<span/>').text(item.title || item.name));
                    }
                    that._initItem($li, item.idx || idx, $ul, item);
                    if(item.children && item.children.length) {
                        that.add($li, item.children);
                    }
                });
                this._initList($ul);
                if(expand && !$ul.hasClass('tree')) {
                    that.expand($ul.parent('li'), disabledAnimate, notStore);
                }
            }
        };

        Tree.prototype.reload = function(data) {
            var that = this;

            if(data) {
                that.$.empty();
                that.add(that.$, data);
            }

            if(that.isPreserve)
            {
                if(that.store.time) {
                    that.$.find('li:not(.tree-action-item)').each(function() {
                        var $li= $(this);
                        that[that.store[$li.data('id')] ? 'expand' : 'collapse']($li, true, true);
                    });
                }
            }
        };

        Tree.prototype._initList = function($list, $parentItem, idx, data) {
            var that = this;
            if(!$list.hasClass('tree')) {
                $parentItem = ($parentItem || $list.closest('li')).addClass('has-list');
                if(!$parentItem.find('.list-toggle').length) {
                    $parentItem.prepend(this.options.toggleTemplate);
                }
                idx = idx || $parentItem.data('idx');
            } else {
                idx = 0;
                $parentItem = null;
            }
            var $children = $list.attr('data-idx', idx || 0).children('li:not(.tree-action-item)').each(function(index) {
                that._initItem($(this), index + 1, $list);
            });
            if($children.length === 1 && !$children.find('ul').length)
            {
                $children.addClass('tree-single-item');
            }
            data = data || ($parentItem ? $parentItem.data() : null);
            var actions = formatActions(data ? data.actions : null, this.actions);
            if(actions) {
                if(actions.add && actions.add.templateInList !== false) {
                    var $actionItem = $list.children('li.tree-action-item');
                    if(!$actionItem.length) {
                        $('<li class="tree-action-item"/>').append(createActionEle(actions.add, 'add', actions.add.templateInList)).appendTo($list);
                    } else {
                        $actionItem.detach().appendTo($list);
                    }
                }
                if(actions.sort) {
                    $list.sortable($.extend({
                        dragCssClass: 'tree-drag-holder', 
                        trigger: '.sort-handler', 
                        selector: 'li:not(.tree-action-item)',
                        finish: function(e) {
                            that.callEvent('action', {action: actions.sort, $list: $list, target: e.target, item: data});
                        }
                    }, actions.sort.options, $.isPlainObject(this.options.sortable) ? this.options.sortable : null));
                }
            }
            if($parentItem && ($parentItem.hasClass('open') || (data && data.open))) {
                $parentItem.addClass('open in');
            }
        };

        Tree.prototype._initItem = function($item, idx, $parentList, data) {
            if(idx === undefined) {
                var $pre = $item.prev('li');
                idx = $pre.length ? ($pre.data('idx') + 1) : 1;
            }
            $parentList = $parentList || $item.closest('ul');
            $item.attr('data-idx', idx).removeClass('tree-single-item');
            if(!$item.data('id')) {
                var id = idx;
                if(!$parentList.hasClass('tree')) {
                    id = $parentList.parent('li').data('id') + '-' + id;
                }
                $item.attr('data-id', id);
            }
            data = data || $item.data();
            var actions = formatActions(data.actions, this.actions);
            if(actions) {
                var $actions = $item.find('.tree-actions');
                if(!$actions.length) {
                    $actions = $('<div class="tree-actions"/>').appendTo(this.options.itemWrapper ? $item.find('.tree-item-wrapper') : $item);
                    $.each(actions, function(actionName, action) {
                        if(action) $actions.append(createActionEle(action, actionName));
                    });
                }
            }

            var $children = $item.children('ul');
            if($children.length) {
                this._initList($children, $item, idx, data);
            }
        };

        Tree.prototype._init = function() {
            var options = this.options, that = this;
            this.actions = formatActions(options.actions);

            this.$.addClass('tree');
            if(options.animate) this.$.addClass('tree-animate');

            this._initList(this.$);

            var initialState = options.initialState;
            var isPreserveEnable = $.zui && $.zui.store && $.zui.store.enable;
            if(isPreserveEnable) {
                this.selector = name + '::' + (options.name || '') + '#' + (this.$.attr('id') || globalId++);
                this.store = $.zui.store[options.name ? 'get' : 'pageGet'](this.selector, {});
            }
            if(initialState === 'preserve') {
                if(isPreserveEnable) this.isPreserve = true;
                else this.options.initialState = initialState = 'normal';
            }

            // init data
            this.reload(options.data);
            if(isPreserveEnable) this.isPreserve = true;

            if(initialState === 'expand') {
                this.expand();
            } else if(initialState === 'collapse') {
                this.collapse();
            }

            // Bind event
            this.$.on('click', '.list-toggle,a[href="#"],.tree-toggle', function(e) {
                var $this = $(this);
                var $li = $this.parent('li');
                that.callEvent('hit', {target: $li, item: $li.data()});
                that.toggle($li);
                if($this.is('a')) e.preventDefault();
            }).on('click', '.tree-action', function() {
                var $action = $(this);
                var action = $action.data();
                if(action.action) action = action.action;
                if(action.type === 'sort') return;
                var $li = $action.closest('li:not(.tree-action-item)');
                that.callEvent('action', {action: action, target: this, $item: $li, item: $li.data()});
            });
        };

        Tree.prototype.preserve = function($li, id, expand) {
            if(!this.isPreserve) return;
            if($li) {
                id = id || $li.data('id');
                expand = expand === undefined ? $li.hasClass('open') : false;
                if(expand) this.store[id] = expand;
                else delete this.store[id];
                this.store.time = new Date().getTime();
                $.zui.store[this.options.name ? 'set' : 'pageSet'](this.selector, this.store);
            } else {
                var that = this;
                this.store = {};
                this.$.find('li').each(function() {
                    that.preserve($(this));
                });
            }
        };

        Tree.prototype.expand = function($li, disabledAnimate, notStore) {
            if($li) {
                $li.addClass('open');
                if(!disabledAnimate && this.options.animate) {
                    setTimeout(function() {
                        $li.addClass('in');
                    }, 10);
                } else {
                    $li.addClass('in');
                }
            } else {
                $li = this.$.find('li.has-list').addClass('open in');
            }
            if(!notStore) this.preserve($li);
            this.callEvent('expand', $li, this);
        };

        Tree.prototype.show = function($lis, disabledAnimate, notStore) {
            var that = this;
            $lis.each(function() {
                var $li = $(this);
                that.expand($li, disabledAnimate, notStore);
                if($li) {
                    var $ul = $li.parent('ul');
                    while($ul && $ul.length && !$ul.hasClass('tree')) {
                        var $parentLi = $ul.parent('li');
                        if($parentLi.length) {
                            that.expand($parentLi, disabledAnimate, notStore);
                            $ul = $parentLi.parent('ul');
                        } else {
                            $ul = false;
                        }
                    }
                }
            });
        };

        Tree.prototype.collapse = function($li, disabledAnimate, notStore) {
            if($li) {
                if(!disabledAnimate && this.options.animate) {
                    $li.removeClass('in');
                    setTimeout(function() {
                        $li.removeClass('open');
                    }, 300);
                } else {
                    $li.removeClass('open in');
                }
            } else {
                $li = this.$.find('li.has-list').removeClass('open in');
            }
            if(!notStore) this.preserve($li);
            this.callEvent('collapse', $li, this);
        };

        Tree.prototype.toggle = function($li) {
            var collapse = ($li && $li.hasClass('open')) || $li === false || ($li === undefined && this.$.find('li.has-list.open').length);
            this[collapse ? 'collapse' : 'expand']($li);
        };

        // Get and init options
        Tree.prototype.getOptions = function(options) {
            this.options = $.extend({}, Tree.DEFAULTS, this.$.data(), options);
            if(this.options.animate === null && this.$.hasClass('tree-animate')) {
                this.options.animate = true;
            }
        };

        Tree.prototype.toData = function($ul, filter) {
            if($.isFunction($ul)) {
                filter = $ul;
                $ul = null;
            }
            $ul = $ul || this.$;
            var that = this;
            return $ul.children('li:not(.tree-action-item)').map(function() {
                var $li = $(this);
                var data = $li.data();
                delete data['zui.droppable'];
                var $children = $li.children('ul');
                if($children.length) data.children = that.toData($children);
                return $.isFunction(filter) ? filter(data, $li) : data;
            }).get();
        };

        // Call event helper
        Tree.prototype.callEvent = function(name, params) {
            var result;
            if($.isFunction(this.options[name])) {
                result = this.options[name](params, this);
            }
            this.$.trigger($.Event(name + '.' + this.name, params));
            return result;
        };

        // Extense jquery element
        $.fn.tree = function(option, params) {
            return this.each(function() {
                var $this = $(this);
                var data = $this.data(name);
                var options = typeof option == 'object' && option;

                if(!data) $this.data(name, (data = new Tree(this, options)));

                if(typeof option == 'string') data[option](params);
            });
        };

        $.fn.tree.Constructor = Tree;

        // Auto call tree after document load complete
        $(function() {
            $('[data-ride="tree"]').tree();
        });
    }(jQuery));

})
